
#ifndef etl_xml_h
#define etl_xml_h

#include "etl_string.h"
#include "etl_list.h"
#include "etl_hash.h"

#include <iostream>
#include <fstream>
#include <stdarg.h>

namespace etl
{

const char XmlTagSet[] = "<%s%s>%s</%s>";
const char XmlTagSetNoMeta[] = "<%s>%s</%s>";
const char XmlMetaStringFormat[] = " %s=\"%s\"";
const char XmlMetaIntFormat[] = " %s=\"%d\"";
const char XmlMetaCharFormat[] = " %s=\"%c\"";
const char XmlMetaLongFormat[] = " %s=\"%ld\"";

const char CommaDelim = ',';
const char SpaceDelim = ' ';
const char QuoteDelim = '"';

/**
 * @brief etl_xml_node is an object used internall by etl to
 * represent one node in a DOM tree.
 */
class etl_xml_node
{
    friend class etl_xml;
public:
    /// Create a etl_xml_node.
    etl_xml_node();

    /// Create a etl_xml_node as a copy of the passed object.
    /// \param obj The object to copy from when creating this.
    etl_xml_node(const etl_xml_node& obj);

    /// Returns the metadata associated with the passed name, on this node.
    /// \param name The metadata name you're interested in.
    etl_string get_meta_data(etl_string name);

    /// Adds metadata to this tag.
    /// \param name The name of the metadata field you'd like added.
    /// \param val The value of the matadate field you are adding.
    int add_meta_data( etl_string name, etl_string value );

    ///Returns a hash of the meta data, keyed using the id string for the data
    etl_hash<etl_string> get_meta_data_set();

    /// Returns the data section for this node.
    etl_string get_data();

    /// Set the data section for this node.
    /// \param newData The new data section for this node.
    int set_data( etl_string newData );

    /// Set this as a copy of the passed object.
    /// \param obj The object we'd like to copy from.
    etl_xml_node& operator =(const etl_xml_node& obj);

    /// Returns a list of etl_xml_node*'s that point to the children of this node.
    etl_list<etl_xml_node*> get_children();

    /// Append a child.
    int append_child( etl_xml_node* newChild );

    ///Returns a etl_xml_node* that points to the parent of this node.
    etl_xml_node* get_parent();

    /// Sets the parent node for this node.
    /// \param parent The new parent for this node.
    int set_parent( etl_xml_node* newParent );

    ///Returns the name of the current tag.
    etl_string get_tag_name();

    ///Sets the tag_name for this node.
    int set_tag_name( etl_string tagName );

    ///Returns a list of the sub tags from this root that match the given tag value
    ///\warning DO NOT have space characters in in the value portion of an attribute, also DO NOT 
    ///have white space between the Name '=' and the Value.
    etl_list<etl_xml_node*> get_matching_tags(etl_string tag);

private:
    /// Copy to this-> from the passed object.
    /// \param obj The object we'd like to copy from.
    etl_xml_node& copy(const etl_xml_node& obj);

    etl_string             tag;
    etl_xml_node*          parent;
    etl_list<etl_xml_node*> children;
};

/// etl_xml is a very simple, non validating DOM xml parser. etl_xml
/// essentially only cares about the "well formedness" of an XML document.
/// <pre>
///   <?xml version="1.0"?>
///   <user_db num_users="3">
///     <user name="tony" age="27"></user>
///     <user name="brad" age="27"></user>
///     <user name="brent" age="27"></user>
///   </user_db>
/// </pre>

class etl_xml
{
public:
    /// Create a generic etl_xml object.
    etl_xml();
    /// Clean up after ourselves.
    virtual ~etl_xml();

    ///Constructor that assumes the passed string is to be used to set the document for this xml object.
    etl_xml(etl_string obj);

    ///Opends the file indicated reads an xml document from that and sets this as our document, returns 0 if successful, 
    ///and -1 if failed
    ///\param fileNameAndPath is the file name and or relative path.
    int OpenAndSetDocument(etl_string fileNameAndPath);

    ///This method returns a list that contains pointers to all nodes, below this node whom contain a tag 
    ///whose value mathes the passed value.
    ///\param tag is the tag we are searching for
    ///\warning a new call to this will destroy the results of old calls, unless tag == "" in which case returns old set.
    etl_list<etl_xml_node*> search_for_all(etl_string tag, etl_xml_node* node);

    ///This Method Searches for the first instance of a tag.
    ///\param tag is the tag we are searching for
    etl_xml_node* search_for_first(etl_string tag, etl_xml_node* node);

    ///This method returns a hash of the first occurance of each desired tag from the passed root down.
    ///\param node is the root of the search.
    ///\param format is the format string indicating how many strings we are searching for
    etl_hash<etl_xml_node*> search_for_first_of_any(etl_xml_node* node, char* format, ...);

    ///This method is intended to return a etl_hash of etl_list of etl_xml_node* which is keyed off the tag for the nodes
    etl_hash<etl_list<etl_xml_node*> > search_for_any(etl_xml_node* node, char* format, ...);   

    /**
     * @brief This method can be used to querry the typical results
     * of search for all.
     * 
     * This method uses search_for_all method and then runs a query
     * against the nodes to determine if for the meta tag indicated
     * if the value is that we desire.
     * @param tag is the tag to search for.
     * @param node is the root of the search
     * @param metaName is the meta value to check.
     * @param valueDesired is the desired value for the meta in the
     *                     return set.
     * @return etl_list<etl_xml_node*> is a sub set of xml tags where
     *         the tag of the node == the tag parameter value and
     *         the meta value of the meta for the node is equal to
     *         the valueDesired parameter.
     * 
     * @note Edge case passing "" as value desired will return nodes
     * where the meta is not defined for that tag.
     */
    etl_list<etl_xml_node*> query_search_for_all(etl_string tag, etl_xml_node* node, etl_string metaName, etl_string valueDesired);
    /**
     * @brief This method can be used to querry the typical results
     * of search for all, and returns the first match.
     * 
     * This method uses search_for_all method and then runs a query
     * against the nodes to determine if for the meta tag indicated
     * if the value is that we desire, if it is it returns this
     * node.
     * @param tag is the tag to search for.
     * @param node is the root of the search
     * @param metaName is the meta value to check.
     * @param valueDesired is the desired value for the meta in the
     *                     return set.
     * 
     * @return etl_xml_node* is a the first node of the set of xml
     *         tags where the tag of the node == the tag parameter
     *         value and the meta value of the meta for the node is
     *         equal to the valueDesired parameter.
     * 
     * @note Edge case passing "" as value desired will return nodes
     * where the meta is not defined for that tag.
     */
    etl_xml_node* query_search_for_first(etl_string tag, etl_xml_node* node, etl_string metaName, etl_string valueDesired);

    /// Set this etl_xml objects document to argument passed into this call.
    /// \param xml_document The xml document we are going to parse.
    int set_document( const char* xml_document );

    /// This function calls build_document (a recursive fuction) and builds an XML document representation
    /// of the tree passed to it via the parent argument.
    /// \param parent Used by the recursing.
    static int get_document(etl_string& doc,etl_xml_node* parent,bool pretty=false);

    /// Build an in memory DOM from our xml document.
    etl_xml_node* parse();

    /// Build an XML document from a DOM tree of etl_xml_node(s).
    /// \param root The root node of the tree of etl_xml_node(s).
    static etl_string create_xml_document( etl_xml_node* root );

    /// Walk our tree and pring out the data at each node.
    /// \param node The node the decent begins with (usually the DOM root).
    /// \param spaces Set this value to control how the tree is printed.
    int print_tree(etl_xml_node* node, int spaces);

    ///This returns a node* to the node indicated by the path
    ///\param commaDelimitedPath is a comma delimited path with out white space to the node desired, in node tags.
    ///\param searchRoot is a pointer to the root node for the search.
    ///\warning you must include the tag of the searchRoot in the path
    ///\warning this will return only the first instance of the node in the path, therefore if multiple tags exist, this will only return the first.
    etl_xml_node* seek(etl_string commaDelimitedPath, etl_xml_node* searchRoot);

private:
    void search_tree(etl_string tag, etl_xml_node* node);
    etl_xml( const etl_xml& obj );
    etl_xml& operator = ( const etl_xml& obj );

    /// This internal method is phase 1 when parsing. Essentially, it tokenizes
    /// the entire XML document.
    int create_tags();

    /// Returns whether the specified tag is a comment.
    bool is_comment_tag( etl_string elem );

    /// This is the HEART of the xml parser. This recursive function actually
    /// builds an n-array tree representation of our XML document.
    /// \param parent Used by the recursion.
    etl_xml_node* build_tree(etl_xml_node* parent);

    /// This is the recursive function that constructs a etl_string given an XML DOM.
    /// \param parent Used by the recursing.
    static int build_document(etl_string& doc,etl_xml_node* parent,int spaces);

    char* doc;
    long doc_len;
    etl_list<etl_string> tag_list;
    etl_list<etl_xml_node*> node_set;
    etl_list<etl_xml_node*> matchingChildren;
};

}

#endif
